using System;
using System.Collections.Generic;
using System.Text;
using Rapier.Kodachi.Shared;
using System.Threading;
using System.IO;
using System.Windows.Forms;
using System.Diagnostics;
using System.Data.OleDb;
using Microsoft.Win32;
using System.Drawing;
//using System.Windows.Input;

namespace Rapier.Kodachi.Core
{
    public class CoreCatalog
    {
        #region Data Members
        private bool initialScan = true;
        private object itemLock = new object();
        private Plugin plugin;
        #endregion

        #region Properties
        private static SystemImageList _SystemImageList;
        public static SystemImageList SystemImageList
        {
            get { return _SystemImageList; }
            set { _SystemImageList = value; }
        }
        #endregion

        #region Constructor
        public CoreCatalog(Plugin plugin)
        {
            this.plugin = plugin;
            _SystemImageList = new SystemImageList(SystemImageListSize.extraLargeIcons);
        }
        #endregion

        #region Catalog Members
        public void LoadItems()
        {
           if (!initialScan)
              return;

           if( Environment.OSVersion.Version.Major < 6 )
           {
              string systemRoot = System.Environment.GetEnvironmentVariable( "SystemRoot" );
              string explorerPath = systemRoot + "\\EXPLORER.EXE";
              Item myComputerItem = new Item( plugin, "Folder", "My Computer" );
              myComputerItem.Properties.Add( "Path", explorerPath );
              myComputerItem.Properties.Add( "Arguments", "/e,::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" );
              myComputerItem.GetImage = delegate()
              {
                 return GetImageFromPath( explorerPath );
              };

              plugin.Host.AddItem(myComputerItem);
           }

            try
            {
                foreach(IndexFolder folder in CorePlugin.CoreSettings.IndexFolders)
                {
                    if(!Directory.Exists(folder.Path))
                        continue;

                    AddFileWatcher(folder);
                    AddPath(folder);
                    if (folder.IsSpecialFolder && (Environment.SpecialFolder)folder.SpecialFolder == Environment.SpecialFolder.StartMenu)
                    {
                        IndexFolder allUsersStartMenu = new IndexFolder(folder);
                        allUsersStartMenu.Path = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders").GetValue("Common Programs").ToString();
                        AddPath(allUsersStartMenu);
                        AddFileWatcher(allUsersStartMenu);
                    }
                }
            }
            catch (Exception ex)
            {
                plugin.Host.NotifyError("Failed indexing core paths.", ex.Message);
            }

            AddDrives();
            AddKodachi();
            initialScan = false;
        }
        #endregion

        #region Internal Methods
        internal void ComputerItem_UpdateDrilledItems(Item item, List<ILookupItem> drilledItems)
        {
            foreach (string drive in Environment.GetLogicalDrives())
            {
                DriveInfo driveInfo = new DriveInfo(drive);
                Item driveItem = CreateDriveItem(driveInfo);
                drilledItems.Add(driveItem);
            }
        }
        #endregion

        #region Private Methods
       private void AddFileWatcher(IndexFolder folder)
       {
          string searchFilter;
          try
          {
             foreach (IndexExtension extension in folder.Extensions)
             {
                if (extension.Extension.Contains("."))
                   searchFilter = "*" + extension.Extension.Substring(extension.Extension.LastIndexOf('.'));
                else
                   searchFilter = "*." + extension.Extension;

                FileSystemWatcher watcher = new FileSystemWatcher(folder.Path, searchFilter);
                watcher.EnableRaisingEvents = false;
                watcher.IncludeSubdirectories = true;
                watcher.Created += new FileSystemEventHandler(watcher_Changed);
                watcher.Renamed += new RenamedEventHandler(watcher_Renamed);
                watcher.Deleted+=new FileSystemEventHandler(watcher_Changed);
                watcher.EnableRaisingEvents = true;
                Debug.Write("Starting filewatcher for path: '" + folder.Path + "'  extensions: " + searchFilter + "\n");
             }
          }
          catch
          {
             Debug.Write("FAILED to start filewatcher for path: '" + folder.Path + "'\n");
          }
       }

        private void AddPath(IndexFolder folder)
        {
            DirectoryInfo directoryInfo = new DirectoryInfo(folder.Path);

            try
            {
                Item folderItem = CreateFolderItem(directoryInfo);
                plugin.Host.AddItem(folderItem);
            }
            catch (Exception ex)
            {
                plugin.Host.NotifyError("Failed adding folder: " + directoryInfo.Name, ex.Message);
            }

            FileInfo[] files;
            string searchFilter;
            foreach (IndexExtension extension in folder.Extensions)
            {
                if(extension.Extension.Contains("."))
                    searchFilter = "*" + extension.Extension.Substring(extension.Extension.LastIndexOf('.')); 
                else
                    searchFilter = "*." + extension.Extension;

                // Try-Catch in case we don't have permission. There's probably a better way....
                try
                {
                    files = directoryInfo.GetFiles(searchFilter, SearchOption.TopDirectoryOnly);
                }
                catch 
                { 
                    continue; 
                }

                foreach (FileInfo fileInfo in files)
                {
                    try
                    {
                        Item fileItem = CreateFileItem(fileInfo);
                        plugin.Host.AddItem(fileItem);
                    }
                    catch (Exception ex)
                    {
                        plugin.Host.NotifyError("Failed adding item: " + fileInfo.Name, ex.Message);
                    }
                }

                foreach (DirectoryInfo dirInfo in directoryInfo.GetDirectories())
                {
                    IndexFolder childFolder = new IndexFolder(folder);
                    childFolder.Path = dirInfo.FullName;
                    AddPath(childFolder);
                }
            }
        }

       void watcher_Renamed(object sender, RenamedEventArgs e)
       {
          FileInfo fileInfo = new FileInfo(e.FullPath);
          Item fileItem = CreateFileItem(fileInfo);
          plugin.Host.AddItem(fileItem);
          FileInfo fileInfoOld = new FileInfo(e.OldFullPath);
          Item fileItemOld = CreateFileItem(fileInfoOld);
          plugin.Host.RemoveItem(fileItemOld);
          Debug.WriteLine("Rename: adding file " + e.FullPath);
          Debug.WriteLine("Rename: removing file " + e.OldFullPath);
       }

       void watcher_Changed(object sender, FileSystemEventArgs e)
       {
          FileInfo fileInfo = new FileInfo(e.FullPath);
          Item fileItem = CreateFileItem(fileInfo);

          switch (e.ChangeType)
          {          
             case WatcherChangeTypes.Created:
                plugin.Host.AddItem(fileItem);
                Debug.WriteLine("File Create: adding file " + e.FullPath);
                break;
             case WatcherChangeTypes.Deleted:
                plugin.Host.AddItem(fileItem);
                Debug.WriteLine("File Delete: removing file " + e.FullPath);
                break;
          }
       }
        
        private void AddFileInfoItem(FileInfo fileInfo, List<Item> items)
        {
            Item item = CreateFileItem(fileInfo);
            AddItem(items, item);
        }

        private Item CreateFileItem(FileInfo fileInfo)
        {
            string itemPath = fileInfo.FullName;
            string fileName = fileInfo.Name;
            if (fileInfo.Extension.Length > 0)
                fileName = fileName.Replace(fileInfo.Extension, String.Empty);

            string shortcutTarget = null;
            string desc = String.Empty;
            try
            {
                desc = _SystemImageList.GetTypeDescription(itemPath);
                if (desc.Equals("SHORTCUT", StringComparison.InvariantCultureIgnoreCase))
                {
                    ShellLink link = new ShellLink(_SystemImageList, itemPath);
                    desc = SystemImageList.GetTypeDescription(link.Target) + " [Shortcut]";
                    shortcutTarget = link.Target;
                }
            }
            catch { }

            Item item = new Item(plugin, "File", fileName, desc);
            item.Properties["Path"] = itemPath;
            if( shortcutTarget != null )
               item.Properties.Add( "ShortcutTarget", shortcutTarget );

            item.GetImage = delegate() { return GetImageFromPath(itemPath); };

            item.UpdateProperties += item_UpdateProperties;
            item_UpdateProperties(item);

            return item;
        }

        private Item CreateFolderItem(DirectoryInfo directoryInfo)
        {
            string itemPath = directoryInfo.FullName;
            Item item = new Item(plugin, "Folder", directoryInfo.Name, "File Folder");

            item.Properties["Path"] = itemPath;
            item.GetImage = delegate() { return GetImageFromPath(itemPath); };
            item.UpdateDrilledItems += folderItem_UpdateDrilledItems;

            if (itemPath.Equals(System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), StringComparison.InvariantCultureIgnoreCase))
            {
                //plugin.HoldKeysWPF[Key.OemTilde] = item;
                plugin.HoldKeys[Keys.Oemtilde] = item;
            }

            return item;
        }

        private void AddItem(List<Item> items, Item item)
        {
            items.Add(item);
        }

        private object GetText(Item item)
        {
            if (item.ItemType == "Drive")
            {
                DriveInfo driveInfo = new DriveInfo(item.Properties["Path"].ToString());
                if (driveInfo.IsReady)
                    return driveInfo.VolumeLabel;
                else
                    return item.Properties["Text"];
            }
            
            string path = item.Properties["Path"].ToString();
            if(!CorePlugin.IsTextFile(item))
            {
                return path;
            }

            StreamReader reader = null;
            try
            {
                reader = File.OpenText(path);
                return reader.ReadToEnd();
            }
            catch
            {
                return path;
            }
            finally
            {
                if (reader != null)
                {
                    reader.Dispose();
                }
            }
        }

       private void AddKodachi()
       {
          Item fileItem = CreateFileItem(new FileInfo(Application.ExecutablePath));
          plugin.Host.AddItem(fileItem);
       }

        private void AddDrives()
        {
            try
            {
               //throw new ApplicationException();

                foreach (string drive in Environment.GetLogicalDrives())
                {
                    DriveInfo driveInfo = new DriveInfo(drive);
                    Item item = CreateDriveItem(driveInfo);
                    plugin.Host.AddItem(item);
                }
            }
            catch (Exception ex)
            {
                plugin.Host.NotifyError("Failed loading Drives", ex.Message);
            }
        }

        private Item CreateDriveItem(DriveInfo driveInfo)
        {
            string text = driveInfo.Name;
            if (driveInfo.DriveType == DriveType.Fixed && driveInfo.IsReady)
                text = driveInfo.VolumeLabel + " (" + text.Replace("\\", "") + ")";

            Item item = new Item(plugin, "Drive", text, driveInfo.DriveType.ToString() + " Drive");
            item.Properties["Text"] = item.Name;
            item.UpdateProperties += item_UpdateProperties;
            item.Properties["Path"] = driveInfo.RootDirectory;
            item.UpdateDrilledItems += folderItem_UpdateDrilledItems;

            item.GetImage = delegate() { return GetImageFromPath(item.Properties["Path"].ToString()); };

            // Set Hold key
            //Key holdKey = (Key)Enum.Parse(typeof(Key), driveInfo.Name.ToUpper()[0].ToString());
            //plugin.HoldKeysWPF[holdKey] = item;
            Keys holdKey2 = (Keys)Enum.Parse(typeof(Keys), driveInfo.Name.ToUpper()[0].ToString());
            plugin.HoldKeys[holdKey2] = item;

            return item;
        }
        #endregion

        #region Public Methods
        public static Bitmap GetImageFromPath(string path)
        {
           int imageIndex = _SystemImageList.IconIndex( path, true, ShellIconStateConstants.ShellIconStateNormal );
           if (imageIndex == -1)
               return null;
            
           Icon icon = _SystemImageList.Icon( imageIndex );
           if (icon != null)
               return icon.ToBitmap();
           else
               return null;
        }
        #endregion

        #region Event Handlers
        private void item_UpdateProperties(Item item)
        {
            item.Properties["Text"] = GetText(item);
            DataObject dataObject = new DataObject();
            if(item.Properties["Text"].ToString().Length > 0)
                dataObject.SetText(item.Properties["Text"].ToString());
            dataObject.SetData(DataFormats.FileDrop, new string[] { item.Properties["Path"].ToString() });
            item.Properties["ClipboardData"] = dataObject;
        }

        private void folderItem_UpdateDrilledItems(Item item, List<ILookupItem> drilledItems)
        {
            if (!item.Properties.ContainsKey("Path"))
                return;

            DirectoryInfo directoryInfo = new DirectoryInfo(item.Properties["Path"].ToString());

            FileInfo[] files;
            try
            {
                // Try-Catch in case we don't have permission. There's probably a better way....
                files = directoryInfo.GetFiles("*.*", SearchOption.TopDirectoryOnly);
            }
            catch
            {
                return;
            }

            foreach (FileInfo fileInfo in files)
            {
                Item fileItem = CreateFileItem(fileInfo);
                drilledItems.Add(fileItem);
            }

            foreach (DirectoryInfo dirInfo in directoryInfo.GetDirectories())
            {
                drilledItems.Add(CreateFolderItem(dirInfo));
            }
        }
        #endregion
    }
}
